// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// Author: wsfuyibing <682805@qq.com>
// Date: 2024-07-18

package src

import (
	"fmt"
	"strconv"
	"strings"
)

// Option
// is an interface used to store command options.
type Option struct {
	assigned, required           bool
	defaultValue                 any
	kind                         Kind
	name, shortName, description string
	value                        string

	vb bool
	vf float64
	vi int64
	vs string
}

// NewOption
// creates an option instance.
func NewOption(name string) *Option {
	return &Option{name: name, kind: KindString}
}

func NewBoolOption(name string) *Option {
	return &Option{name: name, kind: KindBool}
}

func NewFloatOption(name string) *Option {
	return &Option{name: name, kind: KindFloat}
}

func NewIntOption(name string) *Option {
	return &Option{name: name, kind: KindInt}
}

func NewNullOption(name string) *Option {
	return &Option{name: name, kind: KindNull}
}

func (o *Option) Generate() string {
	return o.formatOption()
}

func (o *Option) GetDescription() string { return o.description }

func (o *Option) GetDefaultValue() any { return o.defaultValue }

func (o *Option) GetKind() Kind { return o.kind }

func (o *Option) GetName() string { return o.name }

func (o *Option) GetRequired() bool { return o.required }

func (o *Option) GetShortName() string { return o.shortName }

func (o *Option) GetSpecified() bool { return o.assigned }

func (o *Option) SetDefaultValue(dv any) *Option {
	o.defaultValue = dv
	return o
}

func (o *Option) SetDescription(description string) *Option {
	o.description = strings.TrimSpace(description)
	return o
}

func (o *Option) SetKind(kind Kind) *Option {
	o.kind = kind
	return o
}

func (o *Option) SetRequired(required bool) *Option {
	o.required = required
	return o
}

func (o *Option) SetShortName(c byte) *Option {
	o.shortName = string(c)
	return o
}

func (o *Option) ToBool() bool { return o.vb }

func (o *Option) ToFloat() float64 { return o.vf }

func (o *Option) ToInt() int64 { return o.vi }

func (o *Option) ToString() string { return o.vs }

// +---------------------------------------------------------------------------+
// | Access methods                                                            |
// +---------------------------------------------------------------------------+

// Assign
// user value to option. It's from config file or command line argument.
func (o *Option) assign(value string) {
	o.assigned = true
	o.value = strings.TrimSpace(value)
}

func (o *Option) formatDescription() []string {
	if o.description == "" {
		return []string{}
	}
	return strings.Split(o.description, "\n")
}

func (o *Option) formatOption() (str string) {
	// Ahead with short name.
	if o.shortName == "" {
		// No short name.
		str = "    "
	} else {
		// With short name.
		str = fmt.Sprintf(`-%s, `, o.shortName)
	}

	// Mixed with name.
	str += fmt.Sprintf(`--%s`, o.name)

	// Null value.
	if o.kind == KindNull {
		return
	}

	// Option value.
	val := ""

	// Default value.
	if o.defaultValue != nil {
		val = fmt.Sprintf(`%v`, o.defaultValue)
	}

	// Use type value.
	if val == "" {
		val = o.kind.Value()
	}

	// Append on column.
	if val != "" {
		str += fmt.Sprintf(`=%s`, val)
	}
	return
}

// Validate option.
func (o *Option) validate() (err error) {
	// Use user value that from config file or command-line argument.
	value := o.value

	// Use default value.
	if value == "" && o.defaultValue != nil {
		value = fmt.Sprintf("%v", o.defaultValue)
	}

	// Required.
	if o.value == "" {
		if o.required {
			err = fmt.Errorf(`option is required: --%s`, o.name)
		}
		return
	}

	// Kind is null.
	if o.kind == KindNull {
		err = fmt.Errorf(`option not accept any value: --%s`, o.name)
		return
	}

	// Parse with kind
	switch o.kind {
	case KindBool:
		o.vb, err = strconv.ParseBool(value)
	case KindFloat:
		o.vf, err = strconv.ParseFloat(value, 64)
	case KindInt:
		o.vi, err = strconv.ParseInt(value, 10, 64)
	case KindString:
		o.vs = value
	default:
		return
	}

	if err != nil {
		err = fmt.Errorf(`invalid option value type: --%s`, o.name)
	}
	return
}
